package com.netflix.client;

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.netflix.client.config.CommonClientConfigKey;
import com.netflix.client.config.IClientConfig;

import javax.annotation.Nullable;
import java.net.SocketException;
import java.util.List;
import java.util.Optional;

/**
 * Implementation of RetryHandler created for each request which allows for request
 * specific override
 *
 * Specific 特征、细节的、特定的，也就是说它是和 request 的特征挂钩的重试处理器
 *
 * 这里的意思是，为每个请求都配置自身的重试参数，例如请求 A 允许同一个服务器重试2次，即 retrySameServer =2
 * 请求 B 允许同一个服务器重试 3次，即retrySameServer=3
 *
 */
public class RequestSpecificRetryHandler implements RetryHandler {

    /**
     * 这里的 fallback 相当于降级处理，意思是说，如果当前 request
     * 没有配置这些信息，那么就使用这个 fallback 的重试器
     *
     * 默认赋值其实就是 DefaultLoadBalancerRetryHandler
     */
    private final RetryHandler fallback;
    private int retrySameServer = -1;
    private int retryNextServer = -1;
    /**
     * 连接失败时是否允许重试，也就是发生了 SocketException 时是否重试
     */
    private final boolean okToRetryOnConnectErrors;
    /**
     * 是否允许所有 Exception 都重试
     */
    private final boolean okToRetryOnAllErrors;

    /**
     * 定义哪些异常属于连接异常
     */
    protected List<Class<? extends Throwable>> connectionRelated = 
            Lists.<Class<? extends Throwable>>newArrayList(SocketException.class);

    /**
     * 判断是否是连接异常
     * @param e
     * @return
     */
    public boolean isConnectionException(Throwable e) {
        return Utils.isPresentAsCause(e, connectionRelated);
    }


    public RequestSpecificRetryHandler(boolean okToRetryOnConnectErrors, boolean okToRetryOnAllErrors) {
        // fallback 设置为 RetryHandler.DEFAULT ，用来做 backup 的重试处理
        this(okToRetryOnConnectErrors, okToRetryOnAllErrors, RetryHandler.DEFAULT, null);    
    }
    
    public RequestSpecificRetryHandler(boolean okToRetryOnConnectErrors, boolean okToRetryOnAllErrors,
                                       RetryHandler baseRetryHandler, @Nullable IClientConfig requestConfig) {
        Preconditions.checkNotNull(baseRetryHandler);
        this.okToRetryOnConnectErrors = okToRetryOnConnectErrors;
        this.okToRetryOnAllErrors = okToRetryOnAllErrors;
        this.fallback = baseRetryHandler;
        /**
         * 这里就是，每一个 retryHandler 都传入一个 IClientConfig
         * 这样就可以实现每个 handler 都拥有一套自己的重试策略
         */
        if (requestConfig != null) {
            requestConfig.getIfSet(CommonClientConfigKey.MaxAutoRetries).ifPresent(
                    value -> retrySameServer = value
            );
            requestConfig.getIfSet(CommonClientConfigKey.MaxAutoRetriesNextServer).ifPresent(
                    value -> retryNextServer = value
            );
        }
    }

    @Override
    public boolean isRetriableException(Throwable e, boolean sameServer) {
        /**
         * 如果开启了所有异常都允许重试，那直接返回 true 重试即可
         */
        if (okToRetryOnAllErrors) {
            return true;
        }
        // ClientException 属于运行过程中就会抛出的异常类型，因此另外加一层判断
        else if (e instanceof ClientException) {
            ClientException ce = (ClientException) e;
            // 如果是 Client异常，且异常类型是 服务端节流 ，那么判断是否开启了相同服务器重试，没开启了则返回 ture
            // 表示可以将此请求到别的服务器上重试
            if (ce.getErrorType() == ClientException.ErrorType.SERVER_THROTTLED) {
                return !sameServer;
            } else {
                return false;
            }
        } 
        else  {
            // 判断是否是连接异常
            return okToRetryOnConnectErrors && isConnectionException(e);
        }
    }

    /**
     * 用 fallback 的来判断是否 电路断路
     * @param e
     * @return
     */
    @Override
    public boolean isCircuitTrippingException(Throwable e) {
        return fallback.isCircuitTrippingException(e);
    }

    /**
     * 获取同一服务器的最大重试次数
     * 如果当前 requestRetryHandler 没有配置，则取 fallback 的
     * @return
     */
    @Override
    public int getMaxRetriesOnSameServer() {
        if (retrySameServer >= 0) {
            return retrySameServer;
        }
        return fallback.getMaxRetriesOnSameServer();
    }

    /**
     * 获取不同服务器的最大重试个数
     * 如果当前 requestRetryHandler 没有配置，则取 fallback 的
     * @return
     */
    @Override
    public int getMaxRetriesOnNextServer() {
        if (retryNextServer >= 0) {
            return retryNextServer;
        }
        return fallback.getMaxRetriesOnNextServer();
    }    
}
